package com.bestcem.xm.componet.redis.service;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.spring.data.connection.RedissonClusterConnection;
import org.redisson.spring.data.connection.RedissonConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceClusterConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    //============================Lock=============================
    public RLock getLock(String lockName) {
        return redissonClient.getLock(lockName);
    }

    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
    }

    public void lock(String lockKey, int leaseTime, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, unit);
    }

    public boolean tryLock(String lockKey, int waitTime, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, unit);
        } catch (InterruptedException e) {
            log.error("tryLock(String lockKey, int waitTime, TimeUnit unit) error", e);
        }
        return false;
    }

    public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            log.error("tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) error", e);
            return false;
        }
    }

    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    public boolean setBit(String key, long offset, boolean isShow) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().setBit(key, offset, isShow);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public boolean getBit(String key, long offset) {
        boolean result = false;
        try {
            result = Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(key, offset));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public void multiSet(Map<String, Integer> maps) {
        redisTemplate.opsForValue().multiSet(maps);
    }

    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    public Long del(List<String> keys) {
        return redisTemplate.delete(keys);
    }

    public Boolean expire(String key, long time, TimeUnit timeUnit) {
        return redisTemplate.expire(key, time, timeUnit);
    }

    public Long lLeftPush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    public Object lLeftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    public Long getExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    public Long incr(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    public Long decr(String key, long delta) {
        //return redisTemplate.opsForValue().increment(key, -delta);
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    public Long decr(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }

    public Boolean expireAt(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    public Object execute(RedisScript<Long> script, List<String> list, int total) {
        return redisTemplate.execute(script, list, total);
    }

    public Object execute(RedisScript<Long> script, List<String> list, int total, int pass) {
        return redisTemplate.execute(script, list, total, pass);
    }

    public byte[] dump(String key) {
        return redisTemplate.dump(key);
    }

    public boolean setIfAbsent(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value));
    }

    public boolean setIfAbsent(String key, Object value, Long time, TimeUnit timeUnit) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit));
    }

    public Long size(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    public Integer append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    //============================String=============================
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    public String getString(String key) {
        if (key == null) {
            return null;
        }
        Object s = redisTemplate.opsForValue().get(key);
        if (s == null) {
            return null;
        }
        return String.valueOf(redisTemplate.opsForValue().get(key));
    }

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key      键
     * @param value    值
     * @param time     时间 time要大于0 如果time小于等于0 将设置无限期
     * @param timeUnit 单位
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //================================Hash=================================
    public Object hashGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    public Map<Object, Object> hashGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public Boolean hashSet(String key, String hashKey, Object value, long time, TimeUnit timeUnit) {
        redisTemplate.opsForHash().put(key, hashKey, value);
        return expire(key, time, timeUnit);
    }

    public void hashSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public Boolean hashSetAll(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {
        redisTemplate.opsForHash().putAll(key, map);
        return expire(key, time, timeUnit);
    }

    public void hashSetAll(String key, Map<String, ?> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    public void hashDel(String key, Object... hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    public Boolean hashHasKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    public Set<Object> hashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    //仅当hashKey不存在时才设置
    //等于hsetnx，不存在的时候才会设置进入true   否则返回false
    public Boolean hashPutIfAbsent(String key, String hashKey, String value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    //对应hincrby
    public Long hashIncr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    //对应hincrby
    public Long hashDecr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, -delta);
    }

    //============================ZSet=============================
    public Double zSetIncr(String key, Object value, Double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    public Double zSetDecr(String key, Object value, Double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, -score);
    }

    public Double zSetScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    public Long zSetAdd(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
        return redisTemplate.opsForZSet().add(key, tuples);
    }

    public Boolean zSetAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public Long zSetCount(String key, double min, double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }

    public Set<Object> zsetRangeByScore(String key, double min, double max, long offset, long count) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
    }

    public Long zsetRemove(String key, String value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }

    //============================set=============================

    /**
     * Set，根据key获取Set中的所有值
     *
     * @param key 键
     * @return Set中的所有值
     */
    public Set<Object> sGet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * Set，存
     *
     * @param key    键
     * @param values 值，可以是多个
     * @return 成功存入个数
     */
    public Long setAdd(String key, Object... values) {
        Long num = redisTemplate.opsForSet().add(key, values);
        if (null == num) {
            return 0L;
        }
        return num;
    }

    /**
     * Set，弹出
     *
     * @param key key
     * @return 一个值
     */
    public Object setPop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * Set，存，并设置过期时间
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功存入个数
     */
    public Long setAddExpire(String key, long time, TimeUnit timeUnit, Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        if (time > 0) {
            expire(key, time, timeUnit);
        }
        if (null == count) {
            return 0L;
        }
        return count;
    }

    /**
     * Set，是否存在某个值
     *
     * @param key   键
     * @param value 值
     * @return true-存在，false-不存在
     */
    public boolean isMember(String key, Object value) {
        Boolean isMember = redisTemplate.opsForSet().isMember(key, value);
        if (null == isMember) {
            return false;
        }
        return isMember;
    }

    /**
     * Set，长度
     *
     * @param key 键
     * @return 长度
     */
    public Long sGetSetSize(String key) {
        Long size = redisTemplate.opsForSet().size(key);
        if (null == size) {
            return 0L;
        }
        return size;
    }

    /**
     * Set，移除值为value的值
     *
     * @param key    键
     * @param values 值，可以是多个
     * @return 实际移除的个数
     */
    public Long setRemove(String key, Object... values) {
        Long count = redisTemplate.opsForSet().remove(key, values);
        if (null == count) {
            return 0L;
        }
        return count;
    }

    //===============================list=================================
    public List<Object> listRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    public Long listSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    public Object listIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    public Long listPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    public Long listPush(String key, Object value, long time, TimeUnit timeUnit) {
        Long index = redisTemplate.opsForList().rightPush(key, value);
        expire(key, time, timeUnit);
        return index;
    }

    public Long listPushAll(String key, Object... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    public Long listPushAll(String key, Long time, TimeUnit timeUnit, Object... values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        expire(key, time, timeUnit);
        return count;
    }

    public Long listRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    public Long lLeftPushAll(String key, Collection<String> value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    public Long lRightPush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    public void lSet(String key, long index, String value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    public String lRightPop(String key) {
        return String.valueOf(redisTemplate.opsForList().rightPop(key));
    }

    public Object rPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 使用scan遍历key
     * 为什么不使用keys 因为Keys会引发Redis锁，并且增加Redis的CPU占用,特别是数据庞大的情况下。
     * 这个命令千万别在生产环境乱用。
     * 支持redis单节点和集群调用
     */
    public Set<String> scanMatch(String matchKey, long size) {
        Set<String> keys = new HashSet<>();
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        Objects.requireNonNull(connectionFactory);
        RedisConnection redisConnection = connectionFactory.getConnection();
        Cursor<byte[]> scan;
        if ((redisConnection instanceof LettuceClusterConnection) || (redisConnection instanceof JedisClusterConnection) || (redisConnection instanceof RedissonClusterConnection)) {
            RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
            Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();
            for (RedisClusterNode next : redisClusterNodes) {
                scan = clusterConnection.scan(next, ScanOptions.scanOptions().match(matchKey).count(size).build());
                while (scan.hasNext()) {
                    keys.add(new String(scan.next()));
                }
                scan.close();
            }
            return keys;
        }
        if ((redisConnection instanceof LettuceConnection) || (redisConnection instanceof JedisConnection) || (redisConnection instanceof RedissonConnection)) {
            scan = redisConnection.scan(ScanOptions.scanOptions().match(matchKey).count(size).build());
            while (scan.hasNext()) {
                //找到一次就添加一次
                keys.add(new String(scan.next()));
            }
            scan.close();
            return keys;
        }
        return keys;
    }

    /**
     * Redis Sunionstore 命令将给定集合的并集存储在指定的集合 destination 中。如果 destination 已经存在，则将其覆盖。
     *
     * @param destinationKey 目标key
     * @param keys           源集合key
     * @return 并集长度
     */
    public Long sunionstore(String destinationKey, String... keys) {
        return redisTemplate.opsForSet().unionAndStore(Arrays.asList(keys), destinationKey);
    }

    /**
     * Redis Sdiffstore 命令将给定集合之间的差集存储在指定的集合中。如果指定的集合 key 已存在，则会被覆盖。
     *
     * @param destinationKey 目标key
     * @param keys           源集合key
     * @return 差集长度
     */
    public Long sdiffstore(String destinationKey, String... keys) {
        return redisTemplate.opsForSet().differenceAndStore(Arrays.asList(keys), destinationKey);
    }
}
